package cn.ratel.db;

import cn.ratel.db.ds.DataSourceProvider;
import cn.ratel.kit.StrKit;
import com.google.common.base.CaseFormat;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Generator {
	private String outputDir;
	private String basePackage;

	public Generator(String outputDir, String basePackage) {
		this.outputDir = outputDir;
		this.basePackage = basePackage;
	}

	// -------- BASE_DAO --------
	private static String headerTpl = "package %s;\n" +
			"\n" +
			"import cn.ratel.db.*;\n" +
			"\n" +
			"/**\n" +
			" * Generated by Ratel\n" +
			" */\n" +
			"@SuppressWarnings(\"serial\")\n" +
			"public abstract class Base%s<M extends Base%s> extends Model<M> implements IBean {\n";
	private static String getterAndSetterTpl = "\n" +
			"    public void set%s(%s %s) {\n" +
			"        set(\"%s\", %s);\n" +
			"    }\n" +
			"\n" +
			"    public %s get%s() {\n" +
			"        return get(\"%s\");\n" +
			"    }\n";
	private static String footerTpl = "\n}";


	// -------- DAO --------
	private static String daoTpl = "package %s;\n" +
			"\n" +
			"import %s.*;\n" +
			"import cn.ratel.db.annotation.*;\n" +
			"\n" +
			"@Model\n" +
			"public class %s extends Base%s<%s> {\n" +
			"    public static final %s dao = new %s();\n" +
			"}\n";

	// -------- CONTROLLER --------
	private static String controllerTpl = "package %s;\n" +
			"\n" +
			"import cn.ratel.route.annotation.*;\n" +
			"\n" +
			"@Controller\n" +
			"public class %sController {\n" +
			"}\n";

	public void generate(DataSourceProvider dsProvider) {
		initTableMeta(dsProvider);

		TableBuilder.getTableMap().forEach((tableName, table) -> {
			String className = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName);
			StringBuilder baseModelResult = new StringBuilder(String.format(headerTpl, basePackage + ".model.base", className, className));

			table.columns.forEach((columnName, column) -> {
				String columnNameUpper = StrKit.firstCharToUpperCase(columnName);
				String type = column.clazz.getName();
				type = type.replace("java.lang.", "");
				baseModelResult.append(String.format(getterAndSetterTpl, columnNameUpper, type, columnName, columnName, columnName, type, columnNameUpper, columnName));
			});
			baseModelResult.append(footerTpl);

			String modelResult = String.format(daoTpl, basePackage + ".model", basePackage + ".model.base", className, className, className, className, className);
			String controllerResult = String.format(controllerTpl, basePackage + ".controller", className);
			try {
				writeToFile(baseModelResult.toString(), outputDir + File.separator + "baseModel", "Base" + className + ".java");
				writeToFile(modelResult, outputDir + File.separator + "model", className + ".java");
				writeToFile(controllerResult, outputDir + File.separator + "controller", className + "Controller" + ".java");
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		});
	}

	private void initTableMeta(DataSourceProvider dsProvider) {
		Connection conn = null;
		try {
			conn = dsProvider.getDataSource().getConnection();
			DatabaseMetaData metaData = conn.getMetaData();
			for (String tableName : TableBuilder.getTableNames(metaData)) {
				Table table = new Table(tableName);
				ResultSet rs = metaData.getColumns(null, null, tableName, null);
				while (rs.next()) {
					TableBuilder.addColumn(table, rs);
				}
				TableBuilder.addTable(table);
				rs.close();
			}
		} catch (SQLException e) {
			throw new RuntimeException("generate error", e);
		} finally {
			DbKit.close(conn);
		}
	}

	private void writeToFile(String content, String dir, String fileName) throws IOException {
		File file = new File(dir);
		if (!file.exists()) {
			file.mkdirs();
		}
		try (FileWriter fw = new FileWriter(dir + File.separator + fileName)) {
			fw.write(content);
		}
	}

	public void setOutputDir(String outputDir) {
		this.outputDir = outputDir;
	}

	public void setBasePackage(String basePackage) {
		this.basePackage = basePackage;
	}
}
